/* domparser.cc
* libxml++ and this file are copyright (C) 2000 by Ari Johnson, and
* are covered by the GNU Lesser General Public License, which should be
* included with libxml++ as the file COPYING.
*/

#include "PreCompile.h"
#include "domparser.h"
#include "../dtd.h"
#include "../nodes/element.h"
#include "../nodes/textnode.h"
#include "../nodes/commentnode.h"
#include "../keepblanks.h"
#include "../exceptions/internal_error.h"

#include <libxml/parserInternals.h>//For xmlCreateFileParserCtxt().

#include <sstream>
#include <iostream>

namespace Xml
{

  DomParser::DomParser()
    : doc_(0)
  {
  }

  DomParser::DomParser(const Glib::ustring& filename,const ParserSettings &settings)
    : doc_(0)
  {
    parse_file(filename,settings);
  }

  DomParser::~DomParser()
  { 
    release_underlying();
  }

  void DomParser::parse_file(const Glib::ustring& filename,const ParserSettings &settings)
  {
    release_underlying(); //Free any existing document.

    KeepBlanks k(KeepBlanks::Default);

    //The following is based on the implementation of xmlParseFile(), in xmlSAXParseFileWithData():
    context_ = xmlCreateFileParserCtxt(filename.c_str());

    if(!context_)
    {
      throw InternalError("Couldn't create parsing context");
    }

    if(context_->directory == 0)
    {
      char* directory = xmlParserGetDirectory(filename.c_str());
      context_->directory = (char*) xmlStrdup((xmlChar*) directory);
    }

    parse_context(settings);
  }

  void DomParser::parse_memory_raw(const unsigned char* contents, size_type bytes_count,const ParserSettings &settings)
  {
    release_underlying(); //Free any existing document.

    KeepBlanks k(KeepBlanks::Default);

    //The following is based on the implementation of xmlParseFile(), in xmlSAXParseFileWithData():
    context_ = xmlCreateMemoryParserCtxt((const char*)contents, (int)bytes_count);

    if(!context_)
    {
      throw InternalError("Couldn't create parsing context");
    }

    parse_context(settings);
  }

  void DomParser::parse_memory(const Glib::ustring& contents,const ParserSettings &settings)
  {
    parse_memory_raw((const unsigned char*)contents.c_str(), contents.bytes(),settings);
  }

  void DomParser::parse_context(const ParserSettings &settings)
  {
    KeepBlanks k(KeepBlanks::Default);

    //The following is based on the implementation of xmlParseFile(), in xmlSAXParseFileWithData():
    //and the implementation of xmlParseMemory(), in xmlSaxParseMemoryWithData().
    initialize_context(settings);

    if(!context_)
    {
      throw InternalError("Context not initialized");
    }

    xmlParseDocument(context_);

    check_for_exception();

    if(!context_->wellFormed)
    {
      release_underlying(); //Free doc_;

      throw ParseError("Document not well-formed.");
    }

    if(context_->errNo != 0)
    {
      std::ostringstream o;
      o << "libxml error " << context_->errNo;

      release_underlying();

      throw ParseError(o.str());
    }

    doc_ = context_->myDoc;
    // This is to indicate to release_underlying that we took the
    // ownership on the doc.
    context_->myDoc = NULL;

    //Free the parse context, but keep the document alive so people can navigate the DOM tree:
    //TODO: Why not keep the context alive too?
    Parser::release_underlying();

    check_for_exception();
  }


  void DomParser::parse_stream(std::istream& in,const ParserSettings &settings)
  {
    release_underlying(); //Free any existing document.

    KeepBlanks k(KeepBlanks::Default);

    context_ = xmlCreatePushParserCtxt(
      0, // setting thoses two parameters to 0 force the parser
      0, // to create a document while parsing.
      0,
      0,
      ""); // here should come the filename. I don't know if it is a problem to let it empty

    if(!context_)
    {
      throw InternalError("Couldn't create parsing context");
    }

    initialize_context(settings);

    //TODO: Shouldn't we use a Glib::ustring here, and some alternative to std::getline()?
    std::string line;
    while(std::getline(in, line))
    {
      // since getline does not get the line separator, we have to add it since the parser cares
      // about layout in certain cases.
      line += '\n';

      xmlParseChunk(context_, line.c_str(), (int)line.size() /* This is a std::string, not a ustring, so this is the number of bytes. */, 0);
    }

    xmlParseChunk(context_, 0, 0, 1);

    check_for_exception();

    if(!context_->wellFormed)
    {
      release_underlying(); //Free doc_;

      throw ParseError("Document not well-formed.");
    }

    if(context_->errNo != 0)
    {
      std::ostringstream o;
      o << "libxml error " << context_->errNo;

      release_underlying();

      throw ParseError(o.str());
    }

    doc_ = context_->myDoc;
    // This is to indicate to release_underlying that we took the
    // ownership on the doc.
    context_->myDoc = NULL;


    //Free the parse context, but keep the document alive so people can navigate the DOM tree:
    //TODO: Why not keep the context alive too?
    Parser::release_underlying();

    check_for_exception();
  }

  void DomParser::release_underlying()
  {
    if(doc_)
    {
      xmlFreeDoc(doc_);
      doc_ = 0;
    }

    Parser::release_underlying();
  }

  _xmlDoc* DomParser::steal_document()
  {
    _xmlDoc *ret = doc_;
    doc_ = 0;
    return ret;
  }

} // namespace Xml


